/* 
JS引擎总要做一番准备工作，这份工作其实就是创建对应的执行上下文
全局执行上下文，函数上下文，与eval上下文
1.全局执行上下文
window对象还是var声明的全局变量的载体
2.函数执行上下文
每当一个函数被调用时都会创建一个函数上下文；需要注意的是，同一个函数被多次调用，都会创建一个新的上下文
执行上下文栈(下文简称执行栈)也叫调用栈，执行栈用于存储代码执行期间创建的所有上下文，具有LIFO（Last In First Out后进先出，也就是先进后出）的特性。


JS代码首次运行，都会先创建一个全局执行上下文并压入到执行栈中，之后每当有函数被调用，都会创建一个新的函数执行上下文并压入栈内；
由于执行栈LIFO的特性，所以可以理解为，JS代码执行完毕前在执行栈底部永远有个全局执行上下文。

执行上下文创建分为创建阶段与执行阶段两个阶段，较为难理解应该是创建阶段，
我们先说创建阶段：
JS执行上下文的创建阶段主要负责三件事：确定this---创建词法环境组件（LexicalEnvironment）---创建变量环境组件（VariableEnvironment）
1.确定this

官方的称呼为This Binding，在全局执行上下文中，this总是指向全局对象，例如浏览器环境下this指向window对象。

而在函数执行上下文中，this的值取决于函数的调用方式，如果被一个对象调用，那么this指向这个对象。否则this一般指向全局对象window或者undefined（严格模式）。

在执行上下文创建阶段，函数声明与var声明的变量在创建阶段已经被赋予了一个值，var声明被设置为了undefined，函数被设置为了自身函数，而let  const被设置为未初始化。
变量对象与活动对象的概念是ES3提出的老概念，从ES5开始就用词法环境和变量环境替代了，因为更好解释。

活动对象是函数执行期间实际使用的对象，它包含了函数运行时的所有变量和函数声明。
活动对象是基于变量对象的，在函数执行阶段，变量对象会转变为活动对象。
1.全局执行上下文一般由浏览器创建，代码执行时就会创建；函数执行上下文只有函数被调用时才会创建，调用多少次函数就会创建多少上下文。

2.调用栈用于存放所有执行上下文，满足FILO规则。

3.执行上下文创建阶段分为绑定this，创建词法环境，变量环境三步，两者区别在于词法环境存放函数声明与const let声明的变量，而变量环境只存储var声明的变量。

词法环境由什么组成
词法环境的内部由两部分组成：环境记录器（Environment Record）、对外部环境的引用（outer）
- 环境记录器记录存储变量、函数声明以及函数声明的形参
- 外部环境的引用意味着它可以访问其父级词法环境（作用域）
  环境记录器又分为两种
  - 声明式环境记录（Declarative Environment Record）：用来记录直接有标识符定义的元素，比如变量、常量、let、class、module、import 以及函数声明。
  - 对象式环境记录（Object Environment Record）：主要用于 with 和 global 的词法环境。
    其中声明式环境记录（Declarative Environment Record），又分为两种类型：
    - 函数环境记录（Function Environment Record）：用于函数作用域。
    - 模块环境记录（Module Environment Record）：模块环境记录用于体现一个模块的外部作用域，即模块 export 
- 词法环境可以分为变量环境和词法环境。在ES6中，词法环境分为全局环境和函数环境。
    - 全局环境又分为全局变量环境（Global Variable Environment）
    - 全局函数环境（Global Function Environment），
        - 函数环境又分为函数变量环境（Function Variable Environment）
        - 函数词法环境（Function Lexical Environment）


*/
/* eg1 */
function f1() {
  console.log('听风是风');
};
f1(); //echo

function f1() {
  console.log('echo');
};
f1(); //echo

/* eg2 函数声明改为函数表达式 */
var f1 = function () {
  console.log('听风是风');
};
f1(); //听风是风

var f1 = function() {
  console.log('echo');
};
f1(); //echo


/* eg3 :编译，执行 */
var a = 1;
var b = true;

function foo() {
    console.log(a);
}

foo();
// 在代码执行之前，即编译阶段：

a = undefined;
b = undefined;
foo = function () {
    console.log(a);
};
// 执行阶段：

a = 1;
b = true;
foo = function () {
    console.log(a);
};

/* eg4 */
// 函数优先
// 函数声明和变量声明都会被提升。但是这个值得注意的细节是函数的优先级大于变量

foo();

var foo;

function foo() {
    console.log(1);
}

foo = function () {
    console.log(2);
};
// 答案 输出 1 而不是 undefined 或者 2
// 这段代码会被引起理解为如下形式：

function foo() {
    console.log(1);
}
// var foo 被忽略
foo(); // 1
foo = function () {
    console.log(2);
};
/* eg4  */
// 注意，var foo 尽管出现在 function foo() ... 的声明之前，但函数声明的优先级大于变量提升，
// 即使它写在函数前面，但是还是会以函数为依据展示（变量被忽略）

foo();

function foo() {
    console.log(1);
}

var foo = function () {
    console.log(2);
};

function foo() {
    console.log(3);
}
// 答案 输出 3

/* eg 5  */
// 说到函数声明和变量声明，我们可以举出很多例子，例如这个例子
function bar() {
    console.log('bar1');
}

var bar = function () {
    console.log('bar2');
};

bar();
// 答案 bar2
/* eg6 */
// 调换顺序呢：
var bar = function () {
    console.log('bar2');
};

function bar() {
    console.log('bar1');
}

bar();
// 答案 bar2

// 本质上这些题目绕不开之前俺们说的原理：
// 编译阶段进行函数、变量提升，执行阶段在原处执行代码。
// 在编译阶段函数 bar 提升，执行阶段，
// bar 赋值给 function() {...}，输出结果 bar2